﻿/* eslint-disable camelcase */
import pinyin_dict_firstletter from './pinyin_dict_firstletter'
import pinyin_dict_notone from './pinyin_dict_notone'
import pinyin_dict_withtone from './pinyin_dict_withtone'

/**
 * 汉字与拼音互转工具，根据导入的字典文件的不同支持不同
 * 对于多音字目前只是将所有可能的组合输出，准确识别多音字需要完善的词库，而词库文件往往比字库还要大，所以不太适合web环境。
 * @start 2016-09-26
 * @last 2016-09-29
 */
const toneMap = {
  ā: 'a1',
  á: 'a2',
  ǎ: 'a3',
  à: 'a4',
  ō: 'o1',
  ó: 'o2',
  ǒ: 'o3',
  ò: 'o4',
  ē: 'e1',
  é: 'e2',
  ě: 'e3',
  è: 'e4',
  ī: 'i1',
  í: 'i2',
  ǐ: 'i3',
  ì: 'i4',
  ū: 'u1',
  ú: 'u2',
  ǔ: 'u3',
  ù: 'u4',
  ü: 'v0',
  ǖ: 'v1',
  ǘ: 'v2',
  ǚ: 'v3',
  ǜ: 'v4',
  ń: 'n2',
  ň: 'n3',
  '': 'm2',
}

/** 存储所有字典数据 */
const dict = {
  pinyin_dict_firstletter,
  pinyin_dict_notone,
  pinyin_dict_withtone,
}

const loadResource = async removeTone => {
  dict.notone = {}
  dict.py2hz = dict.pinyin_dict_notone // 拼音转汉字
  Object.entries(dict.pinyin_dict_notone).forEach(([key, value]) => {
    const temp = dict.pinyin_dict_notone[key]
    for (let j = 0, len = temp.length; j < len; j++) {
      if (!dict.notone[temp[j]]) {
        dict.notone[temp[j]] = key // 不考虑多音字
      }
    }
  })
  dict.withtone = {} // 汉字与拼音映射，多音字用空格分开，类似这种结构：{'大': 'da tai'}
  const temp = dict.pinyin_dict_withtone.split(',')
  for (let i = 0, len = temp.length; i < len; i++) {
    // 这段代码耗时28毫秒左右，对性能影响不大，所以一次性处理完毕
    dict.withtone[String.fromCharCode(i + 19968)] = temp[i] // 这里先不进行split(' ')，因为一次性循环2万次split比较消耗性能
  }

  // 拼音 -> 汉字
  if (dict.pinyin_dict_notone) {
    // 对于拼音转汉字，我们优先使用pinyin_dict_notone字典文件
    // 因为这个字典文件不包含生僻字，且已按照汉字使用频率排序
    dict.py2hz = dict.pinyin_dict_notone // 拼音转汉字
  } else {
    // 将字典文件解析成拼音->汉字的结构
    // 与先分割后逐个去掉声调相比，先一次性全部去掉声调然后再分割速度至少快了3倍，前者大约需要120毫秒，后者大约只需要30毫秒（Chrome下）
    let notone = removeTone(dict.pinyin_dict_withtone).split(',')
    let py2hz = {}
    let py
    let hz
    for (let i = 0, len = notone.length; i < len; i++) {
      hz = String.fromCharCode(i + 19968) // 汉字
      py = notone[i].split(' ') // 去掉了声调的拼音数组
      for (let j = 0; j < py.length; j++) {
        py2hz[py[j]] = (py2hz[py[j]] || '') + hz
      }
    }
    dict.py2hz = py2hz
  }
}

loadResource()

/** 简单数组去重 */
function simpleUnique(array) {
  let result = []
  let hash = {}
  for (let i = 0; i < array.length; i++) {
    let key = typeof array[i] + array[i]
    if (!hash[key]) {
      result.push(array[i])
      hash[key] = true
    }
  }
  return result
}

/**
 * 处理多音字，将类似['D', 'ZC', 'F']转换成['DZF', 'DCF']
 * 或者将 ['chang zhang', 'cheng'] 转换成 ['chang cheng', 'zhang cheng']
 */
function handlePolyphone(array, splitter, joinChar) {
  splitter = splitter || ''
  let result = ['']
  let temp = []
  for (let i = 0; i < array.length; i++) {
    temp = []
    let t = array[i].split(splitter)
    for (let j = 0; j < t.length; j++) {
      for (let k = 0; k < result.length; k++) {
        temp.push(result[k] + (result[k] ? joinChar : '') + t[j])
      }
    }
    result = temp
  }
  return simpleUnique(result)
}

/**
 * 根据词库找出多音字正确的读音
 * 这里只是非常简单的实现，效率和效果都有一些问题
 * 推荐使用第三方分词工具先对句子进行分词，然后再匹配多音字
 * @param chinese 需要转换的汉字
 * @param result 初步匹配出来的包含多个发音的拼音结果
 * @param splitter 返回结果拼接字符
 * @param withtone
 */
function parsePolyphone(chinese, result, splitter, withtone, removeTone) {
  let poly = window.pinyin_dict_polyphone
  let max = 7 // 最多只考虑7个汉字的多音字词，虽然词库里面有10个字的，但是数量非常少，为了整体效率暂时忽略之
  let temp = poly[chinese]
  if (temp) {
    // 如果直接找到了结果
    temp = temp.split(' ')
    for (let i = 0; i < temp.length; i++) {
      result[i] = temp[i] || result[i]
      if (!withtone) {
        result[i] = removeTone(result[i])
      }
    }
    return result.join(splitter)
  }
  for (let i = 0; i < chinese.length; i++) {
    temp = ''
    for (let j = 0; j < max && i + j < chinese.length; j++) {
      if (!/^[\u2E80-\u9FFF]+$/.test(chinese[i + j])) break // 如果碰到非汉字直接停止本次查找
      temp += chinese[i + j]
      let res = poly[temp]
      if (res) {
        // 如果找到了多音字词语
        res = res.split(' ')
        for (let k = 0; k <= j; k++) {
          if (res[k]) {
            result[i + k] = withtone ? res[k] : removeTone(res[k])
          }
        }
        break
      }
    }
  }
  // 最后这一步是为了防止出现词库里面也没有包含的多音字词语
  for (let i = 0; i < result.length; i++) {
    result[i] = result[i].replace(/ .*$/g, '')
  }
  return result.join(splitter)
}

const pinyinUtil = {
  /**
   * 根据汉字获取拼音，如果不是汉字直接返回原字符
   * @param chinese 要转换的汉字
   * @param splitter 分隔字符，默认用空格分隔
   * @param withtone 返回结果是否包含声调，默认是
   * @param polyphone 是否支持多音字，默认否
   */
  getPinyin(chinese, splitter = '', withtone = false, polyphone = false) {
    if (!chinese || /^ +$/g.test(chinese)) return ''
    let result = []
    if (dict.withtone) {
      // 优先使用带声调的字典文件
      let noChinese = ''
      for (let i = 0, len = chinese.length; i < len; i++) {
        let pinyin = dict.withtone[chinese[i]]
        if (pinyin) {
          // 如果不需要多音字，默认返回第一个拼音，后面的直接忽略
          // 所以这对数据字典有一定要求，常见字的拼音必须放在最前面
          if (!polyphone) pinyin = pinyin.replace(/ .*$/g, '')
          if (!withtone) pinyin = pinyinUtil.removeTone(pinyin) // 如果不需要声调
          // 空格，把noChinese作为一个词插入
          if (noChinese) {
            result.push(noChinese)
            noChinese = ''
          }
          result.push(pinyin)
        } else if (!chinese[i] || /^ +$/g.test(chinese[i])) {
          // 空格，把noChinese作为一个词插入
          if (noChinese) {
            result.push(noChinese)
            noChinese = ''
          }
        } else {
          noChinese += chinese[i]
        }
      }
      if (noChinese) {
        result.push(noChinese)
        noChinese = ''
      }
    } else if (dict.notone) {
      // 使用没有声调的字典文件
      if (withtone) console.warn('pinyin_dict_notone 字典文件不支持声调！')
      if (polyphone) console.warn('pinyin_dict_notone 字典文件不支持多音字！')
      let noChinese = ''
      for (let i = 0, len = chinese.length; i < len; i++) {
        const temp = chinese.charAt(i)
        const pinyin = dict.notone[temp]
        if (pinyin) {
          // 插入拼音
          // 空格，把noChinese作为一个词插入
          if (noChinese) {
            result.push(noChinese)
            noChinese = ''
          }
          result.push(pinyin)
        } else if (!temp || /^ +$/g.test(temp)) {
          // 空格，插入之前的非中文字符
          if (noChinese) {
            result.push(noChinese)
            noChinese = ''
          }
        } else {
          // 非空格，关联到noChinese中
          noChinese += temp
        }
      }

      if (noChinese) {
        result.push(noChinese)
        noChinese = ''
      }
    } else {
      throw Error('抱歉，未找到合适的拼音字典文件！')
    }
    if (!polyphone) return result.join(splitter)

    if (dict.pinyin_dict_polyphone) {
      return parsePolyphone(chinese, result, splitter, withtone, pinyinUtil.removeTone)
    }
    return handlePolyphone(result, ' ', splitter)
  },
  /**
   * 获取汉字的拼音首字母
   * @param str 汉字字符串，如果遇到非汉字则原样返回
   * @param polyphone 是否支持多音字，默认false，如果为true，会返回所有可能的组合数组
   */
  getFirstLetter(str, polyphone) {
    polyphone = polyphone === undefined ? false : polyphone
    if (!str || /^ +$/g.test(str)) return ''
    if (dict.pinyin_dict_firstletter) {
      // 使用首字母字典文件
      let result = []
      for (let i = 0; i < str.length; i++) {
        let unicode = str.charCodeAt(i)
        let ch = str.charAt(i)
        if (unicode >= 19968 && unicode <= 40869) {
          ch = dict.pinyin_dict_firstletter.all.charAt(unicode - 19968)
          if (polyphone) ch = dict.pinyin_dict_firstletter.polyphone[unicode] || ch
        }
        result.push(ch)
      }
      if (!polyphone) return result.join('') // 如果不用管多音字，直接将数组拼接成字符串
      return handlePolyphone(result, '', '') // 处理多音字，此时的result类似于：['D', 'ZC', 'F']
    }

    let py = pinyinUtil.getPinyin(str, ' ', false, polyphone)
    py = py instanceof Array ? py : [py]
    let result = []
    for (let i = 0; i < py.length; i++) {
      result.push(py[i].replace(/(^| )(\w)\w*/g, (m, $1, $2) => $2.toUpperCase()))
    }
    if (!polyphone) return result[0]
    return simpleUnique(result)
  },
  /**
   * 拼音转汉字，只支持单个汉字，返回所有匹配的汉字组合
   * @param pinyin 单个汉字的拼音，可以包含声调
   */
  getHanzi(pinyin) {
    if (!dict.py2hz) {
      throw Error('抱歉，未找到合适的拼音字典文件！')
    }
    return dict.py2hz[pinyinUtil.removeTone(pinyin)] || ''
  },
  /**
   * 获取某个汉字的同音字，本方法暂时有问题，待完善
   * @param hz 单个汉字
   * @param sameTone 是否获取同音同声调的汉字，必须传进来的拼音带声调才支持，默认false
   */
  getSameVoiceWord(hz, sameTone) {
    sameTone = sameTone || false
    return pinyinUtil.getHanzi(pinyinUtil.getPinyin(hz, ' ', false))
  },
  /**
   * 去除拼音中的声调，比如将 xiǎo míng tóng xué 转换成 xiao ming tong xue
   * @param pinyin 需要转换的拼音
   */
  removeTone(pinyin) {
    return pinyin.replace(/[āáǎàōóǒòēéěèīíǐìūúǔùüǖǘǚǜńň]/g, m => toneMap[m][0])
  },
  /**
   * 将数组拼音转换成真正的带标点的拼音
   * @param pinyinWithoutTone 类似 xu2e这样的带数字的拼音
   */
  getTone(pinyinWithoutTone) {
    let newToneMap = {}
    Object.entries(toneMap).forEach(([key, value]) => {
      newToneMap[value] = key
    })
    return (pinyinWithoutTone || '').replace(/[a-z]\d/g, m => newToneMap[m] || m)
  },
}

export default pinyinUtil
